commonlibsse_ng\rel\module/
runtime.rs

1// C++ Original code
2// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/Module.h
3// - load_segments, clear: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/Module.cpp
4// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
5// SPDX-License-Identifier: MIT
6//
7// SPDX-FileCopyrightText: (C) 2025 SARDONYX
8// SPDX-License-Identifier: Apache-2.0 OR MI
9
10//! Defines Runtime(e.g. `Runtime::Ae`) types.
11
12use crate::rel::version::Version;
13
14/// Defines Skyrim runtime versions.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub enum Runtime {
17    /// The Skyrim runtime is a post-Anniversary Edition Skyrim SE release (version 1.6.x and later).
18    Ae = 1,
19    /// The Skyrim runtime is a pre-Anniversary Edition Skyrim SE release (version 1.5.97 and prior).
20    Se = 1 << 1,
21    /// The Skyrim runtime is Skyrim VR.
22    Vr = 1 << 2,
23}
24
25impl Runtime {
26    /// Get the runtime from version.
27    ///
28    /// This function takes a `Version` object and returns the corresponding `Runtime` variant.
29    ///
30    /// The runtime is determined based on the version's `minor` numbers:
31    /// - `minor` 4 -> `Runtime::Vr` (Skyrim VR)
32    /// - `minor` 6 -> `Runtime::Ae` (Skyrim Anniversary Edition)
33    /// - Any other version is considered `Runtime::Se` (Skyrim Special Edition).
34    ///
35    /// If you want strictness, use `Runtime::from_version_strict`.
36    ///
37    /// # Example
38    ///
39    /// ```
40    /// use commonlibsse_ng::rel::module::Runtime;
41    /// use commonlibsse_ng::rel::version::Version;
42    ///
43    /// let version = Version::new(1, 5, 50, 0); // SE version
44    /// let runtime = Runtime::from_version(&version);
45    /// assert_eq!(runtime, Runtime::Se);
46    ///
47    /// let version = Version::new(1, 6, 317, 0); // AE version
48    /// let runtime = Runtime::from_version(&version);
49    /// assert_eq!(runtime, Runtime::Ae);
50    /// ```
51    ///
52    /// # Laxity of judgment.
53    /// This judgment is incorrectly determined to be Vr if the SE is 1.4.2.
54    ///
55    /// This method is useful under the following assumptions
56    /// - SE users are using the latest (1.5.97).
57    /// - The version update of this library has not caught up with the version of Skyrim, even though the version of Skyrim has been upgraded.
58    #[inline]
59    pub const fn from_version(version: &Version) -> Self {
60        match version.minor() {
61            4 => Self::Vr,
62            6 => Self::Ae,
63            _ => Self::Se,
64        }
65    }
66
67    /// Get the runtime from version, strictly matching predefined database versions.
68    ///
69    /// This function will only return a runtime if the version matches exactly one of the
70    /// predefined versions in the database.
71    ///
72    /// # Example
73    ///
74    /// ```
75    /// use commonlibsse_ng::rel::module::Runtime;
76    /// use commonlibsse_ng::rel::version::Version;
77    /// use commonlibsse_ng::skse::version::{RUNTIME_SSE_1_5_50, RUNTIME_SSE_1_6_317, RUNTIME_VR_1_4_15};
78    ///
79    /// // SE version within range
80    /// let runtime = Runtime::from_version_strict(&RUNTIME_SSE_1_5_50);
81    /// assert_eq!(runtime, Some(Runtime::Se));
82    ///
83    /// // AE version within range
84    /// let runtime = Runtime::from_version_strict(&RUNTIME_SSE_1_6_317);
85    /// assert_eq!(runtime, Some(Runtime::Ae));
86    ///
87    /// // VR version
88    /// let runtime = Runtime::from_version_strict(&RUNTIME_VR_1_4_15);
89    /// assert_eq!(runtime, Some(Runtime::Vr));
90    /// ```
91    pub const fn from_version_strict(version: &Version) -> Option<Self> {
92        use crate::skse::version::*;
93
94        // Match specific predefined version constants for SE, AE, and VR
95
96        Some(match *version {
97            // SE versions (1.1.47 to 1.5.97)
98            RUNTIME_SSE_1_1_47 | RUNTIME_SSE_1_1_51 | RUNTIME_SSE_1_2_36 | RUNTIME_SSE_1_2_39
99            | RUNTIME_SSE_1_3_5 | RUNTIME_SSE_1_3_9 | RUNTIME_SSE_1_4_2 | RUNTIME_SSE_1_5_3
100            | RUNTIME_SSE_1_5_16 | RUNTIME_SSE_1_5_23 | RUNTIME_SSE_1_5_39 | RUNTIME_SSE_1_5_50
101            | RUNTIME_SSE_1_5_53 | RUNTIME_SSE_1_5_62 | RUNTIME_SSE_1_5_73 | RUNTIME_SSE_1_5_80
102            | RUNTIME_SSE_1_5_97 => Self::Se,
103
104            // AE versions (1.6.0 to 1.6.1170)
105            RUNTIME_SSE_1_6_317 | RUNTIME_SSE_1_6_318 | RUNTIME_SSE_1_6_323
106            | RUNTIME_SSE_1_6_342 | RUNTIME_SSE_1_6_353 | RUNTIME_SSE_1_6_629
107            | RUNTIME_SSE_1_6_640 | RUNTIME_SSE_1_6_659 | RUNTIME_SSE_1_6_678
108            | RUNTIME_SSE_1_6_1130 | RUNTIME_SSE_1_6_1170 => Self::Ae,
109
110            // VR version (1.4.15)
111            RUNTIME_VR_1_4_15 => Self::Vr,
112            _ => return None,
113        })
114    }
115
116    /// Is the current Skyrim runtime the Anniversary Edition (AE)?
117    #[inline]
118    pub fn is_ae(&self) -> bool {
119        *self == Self::Ae
120    }
121
122    /// Is the current Skyrim runtime the Special Edition (SE).
123    #[inline]
124    pub fn is_se(&self) -> bool {
125        *self == Self::Se
126    }
127
128    /// Is the current Skyrim runtime the VR version?
129    #[inline]
130    pub fn is_vr(&self) -> bool {
131        *self == Self::Vr
132    }
133}
134
135/// Get `SkyrimSE.exe`/`SkyrimVR.exe` dir path from registry.
136///
137/// If got `None`, get path from registry.
138pub fn get_skyrim_dir(target_runtime: Runtime) -> Option<std::path::PathBuf> {
139    use std::ffi::OsString;
140    use std::os::windows::ffi::OsStringExt;
141    use std::path::PathBuf;
142    use windows::Win32::Foundation::ERROR_SUCCESS;
143    use windows::Win32::System::Registry::{HKEY_LOCAL_MACHINE, REG_ROUTINE_FLAGS, RegGetValueW};
144    use windows::core::h;
145
146    let sub_key = match target_runtime {
147        Runtime::Se | Runtime::Ae => h!(r"SOFTWARE\Bethesda Softworks\Skyrim Special Edition"),
148        Runtime::Vr => h!(r"SOFTWARE\Bethesda Softworks\Skyrim VR"),
149    };
150
151    const BUFFER_SIZE: usize = 4096; // Max NTFS path length
152    let mut value = vec![0_u16; BUFFER_SIZE];
153    let mut length = (BUFFER_SIZE * std::mem::size_of::<u16>()) as u32;
154
155    let status = unsafe {
156        RegGetValueW(
157            HKEY_LOCAL_MACHINE,
158            sub_key,
159            h!("Installed Path"),
160            REG_ROUTINE_FLAGS(0x20002),
161            None,
162            Some(value.as_mut_ptr().cast()),
163            Some(&mut length),
164        )
165    };
166
167    if status != ERROR_SUCCESS {
168        return None;
169    }
170
171    // Convert UTF-16 buffer to PathBuf
172    let path_str =
173        OsString::from_wide(&value[..(length as usize / 2)]).to_string_lossy().to_string();
174
175    Some(PathBuf::from(path_str.trim_end_matches('\0')))
176}
177
178/// Get `SkyrimSE.exe`/`SkyrimVR.exe` path from registry.
179///
180/// If got `None`, get path from registry.
181pub fn get_skyrim_exe_path(target_runtime: Runtime) -> Option<std::path::PathBuf> {
182    let mut install_path = get_skyrim_dir(target_runtime)?;
183    install_path.push(match target_runtime {
184        Runtime::Se | Runtime::Ae => "SkyrimSE.exe",
185        Runtime::Vr => "SkyrimVR.exe",
186    });
187    Some(install_path)
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use crate::skse::version::{RUNTIME_SSE_1_5_50, RUNTIME_SSE_1_6_317, RUNTIME_VR_1_4_15};
194
195    /// Tests the correct conversion from `Version` to `Runtime`.
196    /// This simulates real-world scenarios based on Skyrim's version numbers.
197    #[test]
198    fn test_runtime_enum() {
199        // Checking the enum values by their numeric representation.
200        assert_eq!(Runtime::Ae as u8, 1);
201        assert_eq!(Runtime::Se as u8, 2);
202        assert_eq!(Runtime::Vr as u8, 4);
203
204        assert_eq!(Runtime::from_version(&RUNTIME_SSE_1_5_50), Runtime::Se);
205        assert_eq!(Runtime::from_version(&RUNTIME_SSE_1_6_317), Runtime::Ae);
206        assert_eq!(Runtime::from_version(&RUNTIME_VR_1_4_15), Runtime::Vr);
207
208        let version_1_4_5 = Version::new(1, 4, 5, 0); // Unknown version (not recognized by rules)
209        assert_eq!(Runtime::from_version(&version_1_4_5), Runtime::Vr);
210        assert_eq!(Runtime::from_version_strict(&version_1_4_5), None);
211    }
212
213    #[ignore = "local only"]
214    #[test]
215    fn test_get_skyrim_exe_path() {
216        dbg!(get_skyrim_exe_path(Runtime::Se));
217    }
218}